﻿using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.Generic;

namespace GeoSketch2.Core
{
    /// <summary>
    /// A tools class with useful methods for the program
    /// </summary>
    public static class Tools
    {
        #region Array of points conversion
        /// <summary>
        /// Converts an array of points with integer coordinates to an array of points with double precision coordinates
        /// </summary>
        /// <param name="Points">Source points</param>
        public static Point2D64D[] To64D(this Point2D32S[] Points)
        {
            Point2D64D[] pts = new Point2D64D[Points.Length];
            for (int i = 0; i < Points.Length; i++)
            {
                pts[i] = Points[i];
            }
            return pts;
        }

        /// <summary>
        /// Converts an array of points with single precision coordinates to an array of points with double precision coordinates
        /// </summary>
        /// <param name="Points">Source points</param>
        public static Point2D64D[] To64D(this Point2D32F[] Points)
        {
            Point2D64D[] pts = new Point2D64D[Points.Length];
            for (int i = 0; i < Points.Length; i++)
            {
                pts[i] = Points[i];
            }
            return pts;
        }

        /// <summary>
        /// Converts an array of points with single precision coordinates to an array of points with double precision coordinates
        /// </summary>
        /// <param name="Points">Source points</param>
        public static Point2D64D[] To64D(this PointF[] Points)
        {
            Point2D64D[] pts = new Point2D64D[Points.Length];
            for (int i = 0; i < Points.Length; i++)
            {
                pts[i] = Points[i];
            }
            return pts;
        }

        /// <summary>
        /// Converts an array of points with single precision coordinates to an array of points with double precision coordinates
        /// </summary>
        /// <param name="Points">Source points</param>
        public static PointF[] ToPointF(this Point2D64D[] Points)
        {
            PointF[] pts = new PointF[Points.Length];
            for (int i = 0; i < Points.Length; i++)
            {
                pts[i] = Points[i];
            }
            return pts;
        } 
        #endregion

        #region Bounds
        /// <summary>
        /// Gets the bounds of a set of points
        /// </summary>
        /// <param name="Points">Source points</param>
        /// <param name="YDirection">The direction of the Y axis</param>
        public static RectangleD GetRectangleDBounds(this Point2D64D[] Points,YAxisDirection YDirection)
        {
            double xmin = Points[0].X, xmax = Points[0].X, ymin = Points[0].Y, ymax = Points[0].Y;
            for (int i = 1; i < Points.Length; i++)
            {
                if (Points[i].X < xmin) { xmin = Points[i].X; }
                if (Points[i].X > xmax) { xmax = Points[i].X; }
                if (Points[i].Y < ymin) { ymin = Points[i].Y; }
                if (Points[i].Y > ymax) { ymax = Points[i].Y; }
            }
            if (YDirection == YAxisDirection.DownSide)
            {
                return new RectangleD(xmin, ymin, xmax - xmin, ymax - ymin);
            }
            else
            {
                return new RectangleD(xmin, ymax, xmax - xmin, ymax - ymin,YAxisDirection.UpSide);
            }
        }

        /// <summary>
        /// Gets the bounds of an image in pixels
        /// </summary>
        /// <param name="Image">Source image</param>
        public static RectangleF GetImageBounds(this Image Image)
        {
            return new RectangleF(0, 0, Image.Width, Image.Height);
        }
        #endregion

        #region Y-Coordinate inversion
        /// <summary>
        /// Inverse the y coordinate of a pixel
        /// </summary>
        /// <param name="Point">Source point</param>
        /// <param name="ImageHeight">The height of the image</param>
        public static Point2D64D InverseY(this Point2D64D Point, double ImageHeight)
        {
            return new Point2D64D(Point.X, ImageHeight - 1 - Point.Y);
        }

        /// <summary>
        /// Inverse the y coordinate of pixels
        /// </summary>
        /// <param name="Points">Source points</param>
        /// <param name="ImageHeight">The height of the image</param>
        public static Point2D64D[] InverseY(this Point2D64D[] Points, double ImageHeight)
        {
            Point2D64D[] dst = new Point2D64D[Points.Length];
            for (int i = 0; i < Points.Length; i++)
            {
                dst[i] = InverseY(Points[i], ImageHeight);
            }
            return dst;
        }

        /// <summary>
        /// Inverse the y coordinate of pixels
        /// </summary>
        /// <param name="Points">Source points</param>
        /// <param name="ImageHeight">The height of the image</param>
        public static Point2D[] InverseY(this Point2D[] Points, double ImageHeight)
        {
            Point2D[] dst = (Point2D[])(Points.DeepClone());
            for (int i = 0; i < Points.Length; i++)
            {
                dst[i].Point = InverseY(dst[i].Point, ImageHeight);
            }
            return dst;
        }
        #endregion

        #region Rectangle adjustment
        /// <summary>
        /// Computes a rectangle that fits into the reference rectangle with the aspect ration of the rectangle to fit
        /// </summary>
        /// <param name="ReferenceRectangle">The reference rectangle</param>
        /// <param name="RectangleToFit">The the rectangle to fit</param>
        public static RectangleD FitRectangleIntoRectangle(RectangleD ReferenceRectangle, RectangleD RectangleToFit)
        {
            RectangleD dst = new RectangleD();
            if (RectangleToFit.Height / RectangleToFit.Width < ReferenceRectangle.Height / ReferenceRectangle.Width)
            {
                dst.Width = ReferenceRectangle.Width;
                dst.Height = dst.Width * RectangleToFit.Height / RectangleToFit.Width;
                dst.X = 0;
                dst.Y = (ReferenceRectangle.Height - dst.Height) / 2;
            }
            else
            {
                dst.Height = ReferenceRectangle.Height;
                dst.Width = dst.Height * RectangleToFit.Width / RectangleToFit.Height;
                dst.X = (ReferenceRectangle.Width - dst.Width) / 2;
                dst.Y = 0;
            }
            return dst;
        } 
        #endregion

        #region Cloning
        /*/// <summary>
        /// Makes a deep clone of an object, must be serializable
        /// </summary>
        /// <param name="obj">The object to clone</param>
        public static object DeepClone(this object obj)
        {
            object objResult = null;
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, obj);

                ms.Position = 0;
                objResult = bf.Deserialize(ms);
            }
            return objResult;
        }*/

        /// <summary>
        /// Makes a deep clone of an object, must be serializable
        /// </summary>
        /// <param name="obj">The object to clone</param>
        public static T DeepClone<T>(this T obj)where T:class
        {
            T objResult = null;
            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, obj);

                ms.Position = 0;
                objResult = (T)bf.Deserialize(ms);
            }
            return objResult;
        } 
        #endregion

        #region GraphicsPath conversion
        /// <summary>
        /// Converts an array of points to a graphicsPath
        /// </summary>
        /// <param name="Points">Source points</param>
        public static GraphicsPath ToGraphicsPath(this Point2D64D[] Points)
        {
            return new GraphicsPath(Points.ToPointF(), GetPathPointsType(Points.Length));
        }

        /// <summary>
        /// Gets an array of points type for a graphics path
        /// </summary>
        /// <param name="PointsCount">The number of points</param>
        public static byte[] GetPathPointsType(int PointsCount)
        {
            byte[] points_type = new byte[PointsCount];

            points_type[0] = (byte)PathPointType.Start;
            for (int i = 1; i < PointsCount; i++)
            {
                points_type[i] = (byte)PathPointType.Line;
            }

            return points_type;
        } 
        #endregion

        #region Color tools
        /// <summary>
        /// Gets an image filled by the specified color
        /// </summary>
        /// <param name="Color">The fill color</param>
        /// <param name="SideLength">The length of the side of the image</param>
        public static Bitmap GetColoredSquare(Color Color, int SideLength)
        {
            Bitmap image = new Bitmap(SideLength, SideLength);
            Graphics g = Graphics.FromImage(image);
            g.FillRectangle(new SolidBrush(Color), new Rectangle(0, 0, image.Width, image.Height));
            g.Dispose();
            return image;
        }

        /// <summary>
        /// Gets the named color of the filled image
        /// </summary>
        /// <param name="Image">The image to get the color</param>
        public static Color GetBitmapColor(this Bitmap Image)
        {
            Color dst = Image.GetPixel(Image.Width / 2, Image.Height / 2);
            return dst.GetNamedColor();
        }

        /// <summary>
        /// Gets the named color of a color
        /// </summary>
        /// <param name="color">The color to find the name</param>
        public static Color GetNamedColor(this Color color)
        {
            Array Colors = Enum.GetValues(typeof(KnownColor));
            Color dst;

            foreach (KnownColor eKnownColor in Colors)
            {
                dst = Color.FromKnownColor(eKnownColor);

                if (color.ToArgb() == dst.ToArgb() && !dst.IsSystemColor)
                {
                    return Color.FromKnownColor(eKnownColor);
                }
            }

            return color;
        } 
        #endregion

        #region Get files from extensions
        /// <summary>
        /// Gets the file in a directory, with the specified extensions
        /// </summary>
        /// <param name="Directory">The directory to look in</param>
        /// <param name="Extensions">The extensions to filter (eg *.jpg)</param>
        public static FileInfo[] GetFilesFromExtensions(string Directory, string[] Extensions)
        {
            DirectoryInfo DI = new DirectoryInfo(Directory);
            List<FileInfo> Files = new List<FileInfo>();

            foreach (string s in Extensions)
            {
                Files.AddRange(DI.GetFiles(s));
            }

            return Files.ToArray();
        } 
        #endregion
    }
}
